//********************************************************************
//	created:	16:10:2011   0:48
//	filename: 	sslsocket.cpp
//	author:		tiamo
//	purpose:	ssl socket
//********************************************************************

#include "stdafx.h"
#include "sslsocket.h"

namespace network
{
	//
	// constructor
	//
	SSLSocket::SSLSocket()
	{
		mWouldBlock															= false;
		mSSLContext															= nullptr;
		mSSLMethod															= nullptr;
		mSSLSocket															= nullptr;
	}

	//
	// destructor
	//
	SSLSocket::~SSLSocket()
	{
		if(mSSLSocket)
			SSL_free(mSSLSocket);

		if(mSSLContext)
			SSL_CTX_free(mSSLContext);
	}

	//
	// on connect
	//
	void SSLSocket::onConnected(bool errorState)
	{
		//
		// start ssl nego if connected
		//
		if(!errorState)
		{
			//
			// get ssl method
			//
			mSSLMethod														= SSLv3_method();
			if(!mSSLMethod)
				ThrowException(L"SSLSocket(%p): unable to get sslv3 method", this);

			//
			// create ssl context
			//
			mSSLContext														= SSL_CTX_new(const_cast<SSL_METHOD*>(mSSLMethod));
			if(!mSSLContext)
				ThrowException(L"SSLSocket(%p): unable to create ssl context", this);

			//
			// set ca root
			//
			if(!mCARoot.empty() && SSL_CTX_load_verify_locations(mSSLContext, nullptr, utils::wideToUtf8(mCARoot).c_str()) <= 0)
				ThrowException(L"SSLSocket(%p): unable to set ca root(%ls)", this, mCARoot.c_str());

			//
			// set cert file
			//
			if(!mCertFileName.empty() && SSL_CTX_use_certificate_file(mSSLContext, utils::wideToUtf8(mCertFileName).c_str(), SSL_FILETYPE_PEM) <= 0)
				ThrowException(L"SSLSocket(%p): unable to set cert file(%ls)", this, mCertFileName.c_str());

			//
			// set private key
			//
			if(!mKeyFileName.empty() && SSL_CTX_use_PrivateKey_file(mSSLContext, utils::wideToUtf8(mKeyFileName).c_str(), SSL_FILETYPE_PEM) <= 0)
				ThrowException(L"SSLSocket(%p): unable to set private key file(%ls)", this, mKeyFileName.c_str());

			//
			// create ssl
			//
			mSSLSocket														= SSL_new(mSSLContext);
			if(!mSSLSocket)
				ThrowException(L"SSLSocket(%p): unable to create ssl socket", this);

			//
			// attach socket file to ssl
			//
			if(!SSL_set_fd(mSSLSocket, static_cast<int>(getHandle())))
				ThrowException(L"SSLSocket(%p): unable to set socket", this);

			//
			// try connect
			//
			int ret															= SSL_connect(mSSLSocket);
			int errorCode													= checkAndSetSSLWouldBlockErrorFlags(ret);
			if(errorCode != SSL_ERROR_NONE)
				ThrowException(L"SSLSocket(%p): ssl connect failed (%d:%d)", this, ret, errorCode);
		}

		//
		// call super
		//
		AsyncSocket::onConnected(errorState);
	}

	//
	// real read
	//
	int SSLSocket::realRead(void* buffer, uint32_t readLength)
	{
		//
		// call ssl read
		//
		int ret																= SSL_read(mSSLSocket, buffer, readLength);
		checkAndSetSSLWouldBlockErrorFlags(ret);
		return ret;
	}

	//
	// real write
	//
	int SSLSocket::realWrite(void const* buffer, uint32_t writeLength)
	{
		//
		// call ssl write
		//
		int ret																= SSL_write(mSSLSocket, buffer, writeLength);
		checkAndSetSSLWouldBlockErrorFlags(ret);
		return ret;
	}

	//
	// check would block error
	//
	bool SSLSocket::isWouldBlockError() const
	{
		//
		// call super
		//
		if(AsyncSocket::isWouldBlockError())
			return true;

		return mWouldBlock;
	}

	//
	// check and set ssl would block error flags
	//
	int SSLSocket::checkAndSetSSLWouldBlockErrorFlags(int retValue)
	{
		mWouldBlock															= false;
		int errorValue														= SSL_ERROR_NONE;
		if(retValue < 0)
		{
			errorValue														= SSL_get_error(mSSLSocket, retValue);
			mWouldBlock														= errorValue == SSL_ERROR_WANT_READ || errorValue == SSL_ERROR_WANT_WRITE;
		}

		return mWouldBlock ? SSL_ERROR_NONE : errorValue;
	}

	//
	// set ssl info
	//
	void SSLSocket::setSSLInfo(UniString const& CARoot, UniString const& certFileName, UniString const& keyFileName)
	{
		mCARoot																= CARoot;
		mCertFileName														= certFileName;
		mKeyFileName														= keyFileName;
	}
}
